package cn.com.cetccst.appstore.manager;

import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;

import androidx.annotation.NonNull;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import cn.com.cetccst.appstore.R;
import cn.com.cetccst.appstore.database.AppStoreRepository;
import cn.com.cetccst.appstore.database.model.AppModel;
import cn.com.cetccst.appstore.log.AppStoreLogger;
import cn.com.cetccst.appstore.network.AppStoreHttpRequester;
import cn.com.cetccst.appstore.tools.CommonAlert;
import cn.com.cetccst.network.BaseCallback;
import cn.com.cetccst.tools.network.Connectivity;
import cn.com.cetccst.tools.pm.AppPackage;
import cn.com.cetccst.tools.ui.ThreadSafeToast;
import okhttp3.ResponseBody;

public class ApkDownloadManager {

    private final Map<String, DownLoadHttpCallback> downloadingMap = new HashMap<>();
    private static ApkDownloadManager instance;

    private ApkDownloadManager() {
    }

    public static ApkDownloadManager getInstance() {
        if (instance == null) {
            instance = new ApkDownloadManager();
        }
        return instance;
    }

    public void installDownloadedApk(@NonNull Context context, @NonNull String appId) {
        AppModel appModel = AppStoreRepository.getInstance().getAppByAppId(appId);
        final String extraApkPath = composeDownloadAPKPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode());
        final File file = new File(extraApkPath);
        if (file.exists()) {
            String pName = AppPackage.getPackageNameFromApk(context, extraApkPath);
            if (TextUtils.isEmpty(pName)) {
                file.delete();
            } else {
                AppStoreLogger.info("file already exists!package:" + appModel.getAppSummary().getAppPackageName() + " pname=" + pName + " size=" + file.length());
                AppPackage.installApk(context, extraApkPath);
            }
        } else {
            //被删掉了，重新检查状态（这种情况基本不可能出现）
            appModel.detectAppStatus(context);
            AppStoreRepository.getInstance().updateAppModel(appModel);
        }
    }
    public void requestDownLoadApk(final Context context, @NonNull final String appId) {

        AppModel appModel = AppStoreRepository.getInstance().getAppByAppId(appId);

        final String extraApkPath = composeDownloadAPKPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode());

        //提示网络状态下载
        if (Connectivity.isMobileDataConnected(context)) {
            showMobileDataWarningDialog(context, appModel, extraApkPath);
        } else if (Connectivity.isNetworkConnected(context)) {
            resumeDownLoadFromBreakPoint(context, appModel, extraApkPath);
        } else {
            ThreadSafeToast.show(R.string.store_no_network, Toast.LENGTH_LONG);
        }
    }

    private String composeDownloadAPKPath(Context context, String packageName, int versionCode) {
        File dir = new File(context.getExternalFilesDir(null) + "/apps");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir.getPath() + File.separator + packageName + "_" + versionCode + ".apk";
    }

    private String composeDownloadAPKTempPath(Context context, String packageName, int versionCode) {
        File dir = new File(context.getExternalFilesDir(null) + "/apps");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir.getPath() + File.separator + packageName + "_" + versionCode + ".tmp";
    }


    private void resumeDownLoadFromBreakPoint(final Context context, final AppModel appModel, final String extraApkPath) {
        // support 断点续传
        if (downloadingMap.containsKey(appModel.getAppId())) {
            return;
        }

        detectAppModelRange(context, appModel);

        DownLoadHttpCallback callback = new DownLoadHttpCallback(context, appModel, extraApkPath);

        downloadingMap.put(appModel.getAppId(), callback);

        notifyDownloading(context, appModel);
        AppStoreHttpRequester.getInstance().downLoadApk(appModel.getAppSummary().getAppDownloadUrl(), appModel.getRange(), callback);
    }

    private void detectAppModelRange(Context context, AppModel appModel) {
        String extraApkTempPath = composeDownloadAPKTempPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode());
        final File tempFile = new File(extraApkTempPath);
        long start = 0;
        if (tempFile.exists()) {
            start = tempFile.length();
            AppStoreLogger.info("resume download from :" + start + " pname=" + appModel.getAppSummary().getAppPackageName());
            appModel.setRange(start);
        } else {
            appModel.setRange(0);
        }
    }

    private void notifyDownloadCanceled(Context context, AppModel appModel) {
        appModel.detectAppStatus(context);
        AppStoreRepository.getInstance().updateAppModel(appModel);
    }

    private void notifyDownloading(Context context, @NonNull AppModel appModel) {
        appModel.detectAppStatus(context);
        AppStoreRepository.getInstance().updateAppModel(appModel);
        AppStoreLogger.debug("appId : " + appModel.getAppId() + "range : " + appModel.getRange());
    }

    private void notifyDownloaded(Context context, AppModel appModel) {
        downloadingMap.remove(appModel.getAppId());
        appModel.detectAppStatus(context);
        AppStoreRepository.getInstance().updateAppModel(appModel);
    }

    private void renameDownloadAPKTempPath(Context context, String packageName, int versionCode) {
        try {
            String tempPath = composeDownloadAPKTempPath(context, packageName, versionCode);
            String realPath = composeDownloadAPKPath(context, packageName, versionCode);
            File file = new File(tempPath);
            file.renameTo(new File(realPath));
        } catch (Exception e) {
            AppStoreLogger.error("rename downloaded apk error", e);
        }
    }

    public void removeDownloadedApk(final Context context, final String packageName) {
        new Thread(() -> {
            try {
                File[] files = (new File(context.getExternalFilesDir(null) + "/apps")).listFiles();
                for (int i = 0; i < files.length; i++) {
                    String pName = AppPackage.getPackageNameFromApk(context, files[i].getPath());
                    //同一包名的应用删除
                    if (!TextUtils.isEmpty(pName) && pName.contains(packageName)) {
                        AppStoreLogger.info("remove download file, package=" + pName);
                        files[i].delete();
                    }
                }
            } catch (Exception e) {
                AppStoreLogger.error("delete downloaded apk error ", e);
            }
        }).start();

    }

    private void showMobileDataWarningDialog(final Context context, final AppModel app, final String extraApkPath) {
        String fileSize = String.valueOf(app.getAppSummary().getAppSize() / (1024 * 1024));
        CommonAlert.showAlertDialog(context, String.format(context.getString(R.string.store_download_net_tips_body), fileSize), v -> resumeDownLoadFromBreakPoint(context, app, extraApkPath));
    }

    public void cancelDownload(Context context, String appId) {
        AppModel appModel = AppStoreRepository.getInstance().getAppByAppId(appId);
        notifyDownloadCanceled(context, appModel);
        DownLoadHttpCallback downLoadHttpCallback = downloadingMap.get(appId);
        //需要先清掉callback，否则stop回调detect会有问题
        downloadingMap.remove(appId);
        if (downLoadHttpCallback != null) {
            downLoadHttpCallback.stopDownloading();
        }
    }

    /**
     * detect appmodel的下载状态
     * @param context
     * @param appModel
     */
    public void detectDownloadStatus(Context context, AppModel appModel) {
        if (downloadingMap.containsKey(appModel.getAppId())) {
            appModel.setStatus(AppModel.ApkStatus.STATUS_DOWNLOADING);
            if (context != null) {
                detectAppModelRange(context, appModel);
            }
        } else {
            final String extraApkPath = composeDownloadAPKPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode());
            final File file = new File(extraApkPath);
            if (file.exists()) {
                String pName = AppPackage.getPackageNameFromApk(context, extraApkPath);
                if (!TextUtils.isEmpty(pName)) {
                    appModel.setStatus(AppModel.ApkStatus.STATUS_DOWNLOADED);
                } else {
                    file.deleteOnExit();
                }
            }
        }
    }

    private class DownLoadHttpCallback extends BaseCallback<ResponseBody> {

        private AtomicBoolean running;
        private final String extraApkPath;
        private final AppModel appModel;
        private final Context context;
        private static final long SLEEP_INTERVAL = 50;

        //增加下载更新的频率，下载一个包分30次更新，1024字节的更新频率有性能问题
        private final long blockSize;
        private final long offset;
        private long nextSize;

        public DownLoadHttpCallback(Context context, AppModel appModel, String extraApkPath) {
            this.appModel = appModel;
            this.context = context;
            this.extraApkPath = extraApkPath;
            running = new AtomicBoolean(true);

            //增加下载更新的频率，下载一个包分30次更新，1024字节的更新频率有性能问题
            blockSize = appModel.getAppSummary().getAppSize() / 30;
            offset = appModel.getRange();
            nextSize = appModel.getRange() + blockSize;
        }

        public void stopDownloading() {
            running.set(false);
            notifyDownloadCanceled(context, appModel);
        }

        @Override
        public void onResponse(final ResponseBody data) {
            if (!running.get()) {
                //当下载被取消后，不再需要以下操作。
                return;
            }
            try {
                AppStoreLogger.info("handle downLoad Apk response");
                if (data != null) {
                    InputStream inputStream = data.byteStream();
                    BufferedInputStream bis = new BufferedInputStream(inputStream);
                    //写文件采用append 追加
                    FileOutputStream fos = new FileOutputStream(composeDownloadAPKTempPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode()), true);
                    byte[] buffer = new byte[1024];
                    int len;
                    int totalLength = 0;
                    while ((len = bis.read(buffer)) != -1) {
                        if (!downloadingMap.containsKey(appModel.getAppId()) || !running.get()) {
                            closeStream(inputStream, bis, fos);
                            return;
                        }
                        fos.write(buffer, 0, len);
                        fos.flush();
                        totalLength += len;
                        appModel.setRange(totalLength + offset);
                        //防止频繁刷新页面，否则会造成大量的eventbus事件排队
                        if (appModel.getRange() > nextSize) {
                            notifyDownloading(context, appModel);
                            Thread.sleep(SLEEP_INTERVAL);
                            nextSize += blockSize;
                        }
                    }
                    closeStream(inputStream, bis, fos);

                    renameDownloadAPKTempPath(context, appModel.getAppSummary().getAppPackageName(), appModel.getAppSummary().getAppVersionCode());

                    appModel.setRange(totalLength + offset);

                    notifyDownloaded(context, appModel);

                    long filesize = totalLength + offset;
                    AppStoreLogger.info("completed! package:" + appModel.getAppSummary().getAppPackageName() + " size=" + filesize);

                    if (AppStoreRepository.getInstance().loadAppByAppId(appModel.getAppId()) != null) {
                        AppPackage.installApk(context, extraApkPath);
                    } else {
                        removeDownloadedApk(context, appModel.getAppSummary().getAppPackageName());
                    }

                } else {
                    cancelDownload(context, appModel.getAppId());
                }
            } catch (Exception e) {
                AppStoreLogger.error("download apk error ", e);
                ThreadSafeToast.show(context.getString(R.string.store_http_response, context.getString(R.string.store_error_file_download_failed)), Toast.LENGTH_SHORT);
                cancelDownload(context, appModel.getAppId());
            } finally {
                downloadingMap.remove(appModel.getAppId());
            }
        }

        private void closeStream(InputStream inputStream, BufferedInputStream bis, FileOutputStream fos) throws IOException {
            fos.close();
            bis.close();
            inputStream.close();
        }
    }
}
